﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace GameDriver
{
    /// <summary>
    /// A class used to handle two-dimensional rendering transformations.
    /// Supports changes in zoom, angle and position.
    /// </summary>
    public class Camera
    {
        #region PRIVATE VARIABLES
        private float zoom; //Used to store zoom value as a fraction.
        private float angle; //Used to store the rotation of the camera
        private Vector2 position; //Used to store the position of the camera
        private Matrix transform;  //Used to store the transformation value between updates for point transformation.
        #endregion

        #region PUBLIC VARIABLES AND PROPERTIES
        /// <summary>The zoom value of the camera, must be greater than zero.</summary>
        public float Zoom
        {
            get { return zoom; }
            set { zoom = value; if (zoom < 0.1f) zoom = 0.1f; }
        }
        /// <summary>The angle of the camera in radians.</summary>
        public float Angle
        {
            get { return angle; }
            set { angle = value; }
        }
        /// <summary>The position of the camera in the world (centered)</summary>
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        /// <summary>Retrieves the latest transformation data (calculated in GetTransform)</summary>
        public Matrix Transform
        {
            get { return transform; }
        }
        #endregion

        #region CONSTRUCTOR AND PUBLIC METHODS
        /// <summary>
        /// Create a new camera object with the given transformations.
        /// </summary>
        /// <param name="pos">The initial position of the camera in the world (centered).</param>
        /// <param name="zoom">The initial zoom value of the camera, must be greater than zero.</param>
        /// <param name="angle">The initial angle of the camera in radians.</param>
        public Camera(Vector2 pos, float zoom, float angle)
        {
            Position = pos;
            Zoom = zoom;
            Angle = angle;
        }

        /// <summary>
        /// Translate the camera by the given vector.
        /// </summary>
        /// <param name="v">The vector to be added to the camera's current position.</param>
        public void Move(Vector2 v)
        {
            Position += v;
        }

        /// <summary>
        /// Calculates the transformation data and updates the locally stored variable for point transformations.
        /// </summary>
        /// <param name="viewport">The width and height of the screen as a Vector2.</param>
        /// <returns>The matrix used for transformations.</returns>
        public Matrix GetTransform(Vector2 viewport)
        {
            transform = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
                                         Matrix.Identity *
                                         Matrix.CreateRotationZ(Angle) *
                                         Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
                                         Matrix.CreateTranslation(new Vector3(viewport.X * 0.5f, viewport.Y * 0.5f, 0));
            return transform;
        }

        /// <summary>
        /// Convert a position from one in the world to one relative to the camera.
        /// </summary>
        /// <param name="position">The point to convert.</param>
        /// <param name="viewport">
        /// Optional.  If passed, the translation is recalculated with 
        /// this viewport before the point is converted.
        /// </param>
        /// <returns>The converted point.</returns>
        public Vector2 ToRelativeLocation(Vector2 position, Vector2? viewport = null)
        {
            if (viewport.HasValue)
                GetTransform(viewport.Value);
            return Vector2.Transform(position, transform);
        }
        /// <summary>
        /// Convert a position from one relative to the camera to an absolute position in the world.
        /// </summary>
        /// <param name="position">The point to convert.</param>
        /// <param name="viewport">
        /// Optional.  If passed, the translation is recalculated with 
        /// this viewport before the point is converted.
        /// </param>
        /// <returns>The converted point.</returns>
        public Vector2 ToWorldLocation(Vector2 position, Vector2? viewport = null)
        {
            if (viewport.HasValue)
                GetTransform(viewport.Value);
            return Vector2.Transform(position, Matrix.Invert(transform));
        }
        #endregion
    }
}
